package bsh.meta;

import java.lang.reflect.Modifier;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import jmine.tec.utils.Tuple;
import jmine.tec.utils.UnmodifiableTuple;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.BytecodeGenerator;
import bsh.CompilationState;
import bsh.CompiledScript;
import bsh.FieldDescriptorImpl;
import bsh.Parameter;
import bsh.asm.acessor.ObjectAccessor;
import bsh.asm.acessor.ThisFieldObjectAcessor;
import bsh.exception.CompilationFailedException;

/**
 * @author takeshi
 */
public class UnfinishedTypeImpl extends AbstractTypeWrapper implements TypeWrapper, UnfinishedType {

    private final String clInternalName, sourceCodeName;

    private final TypeWrapper superType;

    private final List<MethodDescriptor> methods = new LinkedList<MethodDescriptor>();

    private final List<MethodDescriptor> protectedMethods = new LinkedList<MethodDescriptor>();

    private final List<FieldDescriptor> fields = new LinkedList<FieldDescriptor>();

    private final List<FieldDescriptor> protectedFields = new LinkedList<FieldDescriptor>();

    private final List<TypeWrapper> interfaces = new LinkedList<TypeWrapper>();

    private static final AtomicInteger CALLSITE_ID_GENERATOR = new AtomicInteger();

    private final FieldDescriptor dynamicsField, mathContextField;

    private final List<List<TypeWrapper>> constructors = new ArrayList<List<TypeWrapper>>();

    private final List<DeclaredConstructor> declaredConstructors = new ArrayList<DeclaredConstructor>();

    private final List<UnfinishedType> innerTypes = new ArrayList<UnfinishedType>();

    private final List<FieldDescriptor> delayedFields = new ArrayList<FieldDescriptor>();

    private final int classModifiers;

    private final Set<Tuple<String, List<TypeWrapper>>> writtenMethods = new HashSet<Tuple<String, List<TypeWrapper>>>();

    /**
     * @param sourceCodeName String
     * @param clName o nome da classe
     * @param superType o super tipo
     * @param ifs interfaces
     * @param modifiers the class modifiers
     */
    public UnfinishedTypeImpl(String sourceCodeName, String clName, TypeWrapper superType, List<? extends TypeWrapper> ifs, int modifiers) {
        super();
        this.classModifiers = modifiers;
        this.sourceCodeName = sourceCodeName;
        this.superType = superType;
        this.clInternalName = clName.replace('.', '/');
        this.interfaces.addAll(ifs);
        final TypeWrapper compiledScript = JavaClassWrapper.wrapperFor(CompiledScript.class);
        if (!this.interfaces.contains(compiledScript)) {
            this.interfaces.add(compiledScript);
        }
        this.fields.addAll(superType.visibleFields());
        this.methods.addAll(superType.visibleMethods());
        for (TypeWrapper implemented : this.interfaces) {
            this.methods.addAll(implemented.visibleMethods());
        }
        this.methods.addAll(superType.protectedMethods());
        if (!Modifier.isInterface(modifiers)) {
            this.dynamicsField = this.addField("__dynamicVariables", Map.class);
            this.mathContextField = this.addField("__mathContext", MathContext.class);
        } else {
            this.dynamicsField = null;
            this.mathContextField = null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public String internalName() {
        return this.clInternalName;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "unfinished class " + this.clInternalName;
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper superType() {
        return this.superType;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends MethodDescriptor> getMethods() {
        return new ArrayList<MethodDescriptor>(this.methods);
    }

    /**
     * {@inheritDoc}
     */
    public String className() {
        return this.clInternalName.replaceAll("[/]", ".");
    }

    /**
     * {@inheritDoc}
     */

    public List<? extends TypeWrapper> interfaces() {
        List<TypeWrapper> list = new LinkedList<TypeWrapper>(this.interfaces);
        return list;
    }

    /**
     * {@inheritDoc}
     */
    public String typeDescription() {
        return "L" + this.clInternalName + ";";
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends FieldDescriptor> visibleFields() {
        return new ArrayList<FieldDescriptor>(this.fields);
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends MethodDescriptor> visibleMethods() {
        return new ArrayList<MethodDescriptor>(this.methods);
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addField(String name, TypeWrapper type) {
        FieldDescriptor fd = new FieldDescriptorImpl(this, type, name);
        this.fields.add(fd);
        return fd;
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addField(String name, TypeWrapper type, int fieldMods) {
        FieldDescriptor fd = new FieldDescriptorImpl(this, type, name, fieldMods);
        this.fields.add(fd);
        return fd;
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addStaticField(String name, TypeWrapper type, final int options) {
        FieldDescriptor fd = new FieldDescriptorImpl(this, type, name) {
            @Override
            public boolean isStatic() {
                return true;
            }

            @Override
            public int asmModifiers() {
                return options | Modifier.STATIC;
            }
        };
        this.fields.add(fd);
        return fd;
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addField(String name, Class<?> type) {
        return this.addField(name, JavaClassWrapper.wrapperFor(type));
    }

    /**
     * {@inheritDoc}
     */
    public boolean isInterface() {
        return (this.classModifiers & Opcodes.ACC_INTERFACE) != 0;
    }

    /**
     * {@inheritDoc}
     */
    public void addMethod(MethodDescriptor method) {
        this.methods.add(method);
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends List<TypeWrapper>> visibleConstructors() {
        return new ArrayList<List<TypeWrapper>>(this.constructors);
    }

    /**
     * {@inheritDoc}
     */
    public int newDynamicInvocationId() {
        return CALLSITE_ID_GENERATOR.getAndIncrement();
    }

    /**
     * {@inheritDoc}
     * 
     * @see bsh.meta.TypeWrapper#protectedFields()
     */
    public Collection<? extends FieldDescriptor> protectedFields() {
        return new ArrayList<FieldDescriptor>(this.protectedFields);
    }

    /**
     * {@inheritDoc}
     * 
     * @see bsh.meta.TypeWrapper#protectedMethods()
     */
    public Collection<? extends MethodDescriptor> protectedMethods() {
        return new ArrayList<MethodDescriptor>(this.protectedMethods);
    }

    /**
     * {@inheritDoc}
     */
    public void loadThis(MethodVisitor visitor, CompilationState state) {
        visitor.visitVarInsn(Opcodes.ALOAD, 0);
        state.pushStack(this);
    }

    /**
     * @return Collection
     */
    public Collection<? extends TypeWrapper> getInnerTypes() {
        return new ArrayList<TypeWrapper>(this.innerTypes);
    }

    /**
     * {@inheritDoc}
     */
    public void addInnerType(UnfinishedInnerType innerType) {
        this.innerTypes.add(innerType);
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper getEnclosingType() {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public String getSourceCodeFileName() {
        return this.sourceCodeName;
    }

    /**
     * {@inheritDoc}
     */
    public void addConstructor(List<? extends TypeWrapper> constructorParameters) {
        ArrayList<TypeWrapper> copy = new ArrayList<TypeWrapper>(constructorParameters);
        if (!constructors.contains(copy)) {
            this.constructors.add(copy);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void addEmptyConstructor() {
        this.addConstructor(new ArrayList<TypeWrapper>());
    }

    /**
     * {@inheritDoc}
     */
    public ObjectAccessor getDynamicVariablesFieldAccessor() {
        return new ThisFieldObjectAcessor(this, dynamicsField);
    }

    /**
     * {@inheritDoc}
     */
    public ObjectAccessor getMathContextFieldAccessor() {
        return new ThisFieldObjectAcessor(this, mathContextField);
    }

    /**
     * @return the dynamicsField
     */
    public FieldDescriptor getDynamicsField() {
        return dynamicsField;
    }

    /**
     * @return the mathContextField
     */
    public FieldDescriptor getMathContextField() {
        return mathContextField;
    }

    /**
     * {@inheritDoc}
     */
    public String simpleName() {
        String name = this.className();
        return name.substring(name.lastIndexOf('.') + 1);
    }

    /**
     * {@inheritDoc}
     */
    public void addConstructorWithBody(Parameter[] params, BytecodeGenerator bytecodeGenerator) {
        this.declaredConstructors.add(new DeclaredConstructor(params, bytecodeGenerator));
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends DeclaredConstructor> getUnfinishedConstructors() {
        return new ArrayList<DeclaredConstructor>(this.declaredConstructors);
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addDelayedField(TypeWrapper variableType, String name, int accPublic) {
        if (this.getField(name) != null) {
            return null;
        }
        FieldDescriptor fd = new FieldDescriptorImpl(this, variableType, name);
        this.delayedFields.add(fd);
        this.fields.add(fd);
        return fd;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends FieldDescriptor> getDelayedFields() {
        return new ArrayList<FieldDescriptor>(this.delayedFields);
    }

    /**
     * {@inheritDoc}
     */
    public void writeMethod(int lineNumber, String methodName, List<? extends TypeWrapper> typeParameters)
            throws CompilationFailedException {
        List<TypeWrapper> copy = new ArrayList<TypeWrapper>(typeParameters);
        Tuple<String, List<TypeWrapper>> tuple = UnmodifiableTuple.newTuple(methodName, copy);
        if (!this.writtenMethods.add(tuple)) {
            throw new CompilationFailedException(lineNumber, "Duplicate method signature with name: " + methodName + " and parameters: "
                    + typeParameters);
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean isMethodImplemented(String methodName, List<? extends TypeWrapper> typeParameters) {
        List<TypeWrapper> copy = new ArrayList<TypeWrapper>(typeParameters);
        Tuple<String, List<TypeWrapper>> tuple = UnmodifiableTuple.newTuple(methodName, copy);
        return this.writtenMethods.contains(tuple);
    }

}
